package com.polaris.live.im.sfs.core

import androidx.annotation.IntDef
import androidx.core.os.bundleOf
import com.blankj.utilcode.util.StringUtils
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.polaris.live.common.util.LogExt
import com.polaris.live.constant.EventConst
import com.polaris.live.im.sfs.ARGUMENTS_CLIENT_TYPE
import com.polaris.live.im.sfs.ARGUMENTS_CMD
import com.polaris.live.im.sfs.ARGUMENTS_DATA
import com.polaris.live.im.sfs.ARGUMENTS_EVENT_TYPE
import com.polaris.live.im.sfs.ARGUMENTS_MESSAGE
import com.polaris.live.im.sfs.ARGUMENTS_PARAMS
import com.polaris.live.im.sfs.ARGUMENTS_SENDER
import com.polaris.live.im.sfs.ARGUMENTS_USER
import com.polaris.live.im.sfs.COMMAND_KICK_USER
import com.polaris.live.im.sfs.SmartFoxConfig
import com.polaris.live.im.sfs.bean.request.HeartbeatRequest
import com.polaris.live.im.sfs.interfaces.OnImConnectListener
import com.polaris.live.im.sfs.interfaces.OnImMessageListener
import com.polaris.live.translation.R.string
import com.polaris.live.utils.postEvent
import com.smartfoxserver.v2.entities.data.SFSObject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import sfs2x.client.SmartFox
import sfs2x.client.core.BaseEvent
import sfs2x.client.core.IEventListener
import sfs2x.client.core.SFSEvent
import sfs2x.client.entities.User
import sfs2x.client.requests.ExtensionRequest
import sfs2x.client.requests.LoginRequest
import sfs2x.client.requests.LogoutRequest
import sfs2x.client.util.ClientDisconnectionReason
import java.net.ConnectException
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

/**
 * 服务连接器
 *
 * 只管sfs的连接与断开
 *
 * @author Created by lucas on 2023/10/18 18:04
 */
class SmartFoxConnector(
    @param:ClientType
    private val type: Int,
    private val onConnectListener: OnImConnectListener,
    private val onImMessageListener: OnImMessageListener
) : IEventListener {

    @Volatile
    private var smartFoxConfig: SmartFoxConfig? = null

    private var smartFoxClient: SmartFox? = null

    @Volatile
    private var isLogin = false

    @Volatile
    private var sfsUserId: Int? = null

    @Volatile
    private var isExiting = false

    @Volatile
    private var reLogging = false

    @Volatile
    private var isConnected = true

    private val reconnectNumber = AtomicInteger()
    private val reLoginNumber = AtomicInteger()

    //转发到新的线程中
    private val singleThreadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

    private var reconnectJob: Job? = null
    private var delayLogoutJob: Job? = null
    private var heartbeatJob: Job? = null

    private fun createClient() {
        smartFoxClient = SmartFox().apply {
            addEventListener(SFSEvent.CONNECTION, this@SmartFoxConnector)
            addEventListener(SFSEvent.CONNECTION_LOST, this@SmartFoxConnector)
            addEventListener(SFSEvent.LOGIN, this@SmartFoxConnector)
            addEventListener(SFSEvent.LOGIN_ERROR, this@SmartFoxConnector)
            addEventListener(SFSEvent.LOGOUT, this@SmartFoxConnector)

            addEventListener(SFSEvent.PUBLIC_MESSAGE, this@SmartFoxConnector)

            addEventListener(SFSEvent.EXTENSION_RESPONSE, this@SmartFoxConnector)
            addEventListener(SFSEvent.ADMIN_MESSAGE, this@SmartFoxConnector)
            addEventListener(SFSEvent.MODERATOR_MESSAGE, this@SmartFoxConnector)

            addEventListener(SFSEvent.ROOM_JOIN, this@SmartFoxConnector)
            addEventListener(SFSEvent.USER_ENTER_ROOM, this@SmartFoxConnector)
            addEventListener(SFSEvent.USER_EXIT_ROOM, this@SmartFoxConnector)
            addEventListener(SFSEvent.ROOM_VARIABLES_UPDATE, this@SmartFoxConnector)
        }
    }

    /**
     * 获取原始的client
     */
    fun getClient(): SmartFox? {
        return smartFoxClient
    }

    /**
     * 连接服务器
     *
     * @param config 配置
     */
    fun connect(config: SmartFoxConfig) {
        if (smartFoxConfig != null && !isExiting) {
            return
        }

        //如果在连接前还没有退出，则强制断开
        logout()

        smartFoxConfig = config

        createClient()

        smartFoxClient?.connect(config.host, config.port)

        //不管如何开启计时器等待重连
        reconnect(true)

        LogExt.logI(TAG, "Connect start %s config = %s", getName(type), config.toString())
    }

    /**
     * 断开服务器连接，可以不管状态进行中断，实际表现为退出登录
     */
    fun disconnect() {
        if (smartFoxConfig == null) {
            return
        }

        LogExt.logI(TAG, "Disconnect start %s", getName(type))

        isExiting = true

        if (isLogin) {
            try {
                smartFoxClient?.send(LogoutRequest())
            } catch (e: Exception) {
                LogExt.logE(TAG, e)
            }

            delayLogoutJob = CoroutineScope(connectDispatcher).launch {
                try {
                    if (!isExiting) {
                        return@launch
                    }

                    delay(LOGOUT_TIMEOUT)
                    LogExt.logI(TAG, "Logout timeout %s", getName(type))

                    logout()
                } finally {
                    if (delayLogoutJob == coroutineContext[Job]) {
                        delayLogoutJob = null
                    }
                }
            }
        } else {
            logout()
        }
    }

    //退出登录后断开
    private fun logout() {
        if (!isExiting || smartFoxConfig == null) {
            return
        }

        resetClient()

        reconnectJob?.cancel()
        delayLogoutJob?.cancel()
        heartbeatJob?.cancel()

        reconnectNumber.set(0)
        reLoginNumber.set(0)
        reLogging = false

        isLogin = false
        sfsUserId = null
        isExiting = false

        smartFoxConfig = null

        onDisconnected(false)
    }

    private fun resetClient() {
        smartFoxClient?.apply {
            removeAllEventListeners()
            if (isConnected || isConnecting) {
                disconnect()
                onDisconnectedReal()
            }
        }
        smartFoxClient = null
    }

    //重连逻辑，当连接丢失后
    private fun reconnect(isAwait: Boolean = false) {
        if (smartFoxConfig == null) {
            return
        }
        if (smartFoxClient?.isConnected == true) {
            return
        }

        isLogin = false

        if (reconnectJob != null) {
            return
        }

        reconnectJob = CoroutineScope(connectDispatcher).launch {
            try {
                if (isAwait) {
                    delay(5.seconds)
                }

                while (isActive) {
                    val smartFoxConfig = smartFoxConfig
                    if (smartFoxConfig != null) {
                        val reconnectNumber = reconnectNumber.getAndIncrement()
                        if (reconnectNumber == NUMBER_MAX_RECONNECT) {
                            connectFailure(ConnectException("Server connection failed."))
                        }

                        resetClient()
                        createClient()

                        smartFoxClient?.connect(smartFoxConfig.host, smartFoxConfig.port)

                        LogExt.logI(TAG, "Reconnect start ${getName(type)} reconnectNumber = $reconnectNumber")
                    }

                    delay(5.seconds)
                }
            } finally {
                if (reconnectJob == coroutineContext[Job]) {
                    reconnectJob = null
                }
            }
        }
    }

    private fun haltReconnect() {
        reconnectJob?.cancel()
        reconnectJob = null
    }

    private fun connectSuccess() {
        haltReconnect()
        reconnectNumber.set(0)

        login()
    }

    private fun connectFailure(throwable: Throwable) {
        LogExt.logE(TAG, throwable)
        onDisconnected(true)
    }

    private fun login() {
        val smartFoxConfig = smartFoxConfig
            ?: return
        if (isLogin) {
            return
        }

        LogExt.logI(TAG, "Login start %s", getName(type))

        val loginRequest = LoginRequest(
            smartFoxConfig.userId,
            smartFoxConfig.userToken,
            smartFoxConfig.zone
        )
        smartFoxClient?.send(loginRequest)
    }

    private fun loginSuccess() {
        reLoginNumber.set(0)
        isLogin = true

        heartbeat()
        onConnectedReal()
        onConnected()
    }

    //心跳
    private fun heartbeat() {
        heartbeatJob = CoroutineScope(connectDispatcher).launch {
            try {
                while (isActive) {
                    val smartFoxClient = smartFoxClient
                    if (smartFoxClient != null && smartFoxClient.isConnected) {
                        smartFoxClient.send(HeartbeatRequest())
                    }
                    delay(INTERVAL_HEARTBEAT.milliseconds)
                }
            } finally {
                if (heartbeatJob == coroutineContext[Job]) {
                    heartbeatJob = null
                }
            }
        }
    }

    //真实连接成功
    private fun onConnectedReal() {
        onConnectListener.onConnectedReal(type)
    }

    //真实断开连接
    private fun onDisconnectedReal() {
        onConnectListener.onDisconnectedReal(type)
    }

    //连接成功
    private fun onConnected() {
        if (isConnected) {
            return
        }
        onConnectListener.onConnected(type)
        isConnected = true
    }

    //连接断开
    private fun onDisconnected(isFailure: Boolean) {
        if (!isConnected) {
            return
        }
        onConnectListener.onDisconnected(type)
        if (isFailure) { //异常断开
            onConnectListener.onDisconnectedFailure(type)
        }
        isConnected = false
    }

    //加入房间成功
    private fun onEnterRoom(roomId: String) {
        onConnectListener.onEnterRoom(type, roomId)
    }

    //断开所有连接
    private fun onKick(connectLost: Boolean, msg: String? = null) {
        onConnectListener.onKick(type, connectLost, msg)
    }

    /**
     * 是否连接服务中
     */
    fun isConnecting(): Boolean {
        return smartFoxConfig != null && !isLogin && !isExiting
    }

    /**
     * 判断服务器是否连接，只有已经连接服务器并且已经登录
     */
    fun isConnected(): Boolean {
        val client = smartFoxClient
        return client != null && client.isConnected && isLogin && !isExiting
    }

    override fun dispatch(event: BaseEvent?) {
        if (event != null) {
            CoroutineScope(singleThreadDispatcher).launch {
                invokeDispatch(event)
            }
        }
    }

    private suspend fun invokeDispatch(event: BaseEvent) {
        try {
            val eventType = event.type
            val arguments = event.arguments
                ?: return

            when (eventType) {
                SFSEvent.CONNECTION -> {
                    val success = arguments["success"]
                    if (success != null && success == true) {
                        connectSuccess()
                    } else {
                        reconnect()

                        Firebase.analytics.postEvent(
                            EventConst.SFS_CONNECT_FAILURE,
                            bundleOf(
                                "type" to getName(type),
                                "message" to "success false",
                                "reason" to success
                            )
                        )
                    }
                }
                SFSEvent.CONNECTION_LOST -> {
                    onDisconnectedReal()

                    val reason = arguments["reason"] as? String
                    if (reason.isNullOrEmpty()) {
                        reconnect()
                    } else {
                        LogExt.logI(TAG, "Disconnect lost %s reason = %s", getName(type), reason)

                        when (reason) {
                            ClientDisconnectionReason.MANUAL -> {
                                //手动断开
                            }
                            ClientDisconnectionReason.UNKNOWN, ClientDisconnectionReason.IDLE -> reconnect()
                            ClientDisconnectionReason.KICK, ClientDisconnectionReason.BAN -> if (type == CLIENT_CHAT) {
                                //只有聊天才处理
                                onKick(true)
                            }
                        }
                    }

                    Firebase.analytics.postEvent(
                        EventConst.SFS_CONNECT_FAILURE,
                        bundleOf(
                            "type" to getName(type),
                            "message" to "Connection lost",
                            "reason" to reason
                        )
                    )
                }
                SFSEvent.LOGIN -> {
                    sfsUserId = (arguments[ARGUMENTS_USER] as? User)?.id

                    loginSuccess()
                }
                SFSEvent.LOGIN_ERROR -> {
                    Firebase.analytics.postEvent(
                        EventConst.SFS_LOGIN_FAILURE,
                        bundleOf(
                            "type" to getName(type),
                            "errorCode" to arguments["errorCode"],
                            "errorMessage" to arguments["errorMessage"]
                        )
                    )

                    reconnect()
                }
                SFSEvent.LOGOUT -> logout()

                //---------- 业务转发开始
                SFSEvent.EXTENSION_RESPONSE,
                SFSEvent.MODERATOR_MESSAGE,
                SFSEvent.ADMIN_MESSAGE -> {
                    val command: Any?
                    val params: Any?
                    if (eventType == SFSEvent.EXTENSION_RESPONSE) {
                        command = arguments[ARGUMENTS_CMD]
                        params = arguments[ARGUMENTS_PARAMS]
                        arguments[ARGUMENTS_DATA] = params
                    } else {
                        command = arguments[ARGUMENTS_MESSAGE]
                        arguments[ARGUMENTS_CMD] = command
                        params = arguments[ARGUMENTS_DATA]
                    }

                    if (command == COMMAND_KICK_USER) {
                        //这里可能出现客户端尝试登出，但是服务端还没有收到登出请求，然后客户端再次登录，导致服务端踢出用户
                        val user = arguments[ARGUMENTS_SENDER] as? User
                        if (sfsUserId == null || user?.id != sfsUserId) {
                            return
                        }

                        val kickParams = arguments[ARGUMENTS_DATA]
                        val kickReason = if (kickParams is SFSObject) {
                            kickParams.getUtfString("reason")
                        } else {
                            null
                        }

                        val finalKickReason = if (kickReason == "Your account has been logged in on another device.") {
                            StringUtils.getString(string.multiple_device_login)
                        } else {
                            kickReason
                        }

                        onKick(false, finalKickReason)
                    } else if (command is String && params is SFSObject) {
                        onMessage(eventType, command, arguments)
                    }
                }

                //---------- 其他事件
                SFSEvent.ROOM_JOIN,
                SFSEvent.USER_ENTER_ROOM,
                SFSEvent.USER_EXIT_ROOM,
                SFSEvent.PUBLIC_MESSAGE,
                SFSEvent.ROOM_VARIABLES_UPDATE -> {
                    onMessage(eventType, eventType, arguments)
                }
            }
        } catch (e: Throwable) {
            LogExt.logE(TAG, e)
        }
    }

    private suspend fun onMessage(event: String, command: String, arguments: MutableMap<String, Any>) {
        arguments[ARGUMENTS_EVENT_TYPE] = event
        arguments[ARGUMENTS_CLIENT_TYPE] = type


        onImMessageListener.onMessage(command, arguments)
    }

    /**
     * 发送请求
     */
    fun sendRequest(request: ExtensionRequest) {
        smartFoxClient?.send(request)
    }

    companion object {

        @Suppress("OPT_IN_USAGE")
        val connectDispatcher = newSingleThreadContext("ConnectDispatcher")

        val TAG = "SmartFoxConnector"

        /**
         * 消息client
         */
        const val CLIENT_CHAT = 0

        /**
         * 直播client
         */
        const val CLIENT_LIVE = 1

        /**
         * 推送client
         */
        const val CLIENT_PUSHER = 2

        fun getName(@ClientType type: Int): String {
            return when (type) {
                CLIENT_CHAT -> "Chat"
                CLIENT_LIVE -> "Live"
                CLIENT_PUSHER -> "Pusher"
                else -> throw IllegalArgumentException("Unknown type: $type")
            }
        }

        const val INTERVAL_HEARTBEAT = 10 * 1000L
        const val LOGOUT_TIMEOUT = 3 * 1000L
        const val NUMBER_MAX_RECONNECT = 4

    }
}

@IntDef(SmartFoxConnector.CLIENT_CHAT, SmartFoxConnector.CLIENT_LIVE, SmartFoxConnector.CLIENT_PUSHER)
annotation class ClientType